#!/usr/bin/env python
#
# Copyright (c) 2008-2011,2013 Wind River Systems, Inc.
#
# SPDX-License-Identifier: LGPL-2.1-only
#
"""convert wrapfuncs.in to wrapper function stubs and tables"""

import datetime
import glob
import sys
import re
import os.path
import string
import subprocess
from templatefile import TemplateFile

class ArgumentList:
    """A (possibly empty) list of arguments"""

    def __init__(self, text):
        "parse a comma-separated argument list (including function prototypes)"
        self.args = []
        self.variadic = False
        self.variadic_decl = ""
        self.variadic_start = ""
        self.variadic_end = ""
        # (void) is an empty list, not a list of a single argument which is void
        if text == "void":
            return

        depth = 0
        accum = ''
        comma_sep = text.split(', ')
        # now, what if there was a comma embedded in an argument?
        for arg in comma_sep:
            lcount = arg.count('(')
            rcount = arg.count(')')
            depth = depth + lcount - rcount
            if (depth > 0):
                accum += arg + ', '
            else:
                self.args.append(Argument(accum + arg))
                accum = ''
        if depth != 0:
            raise Exception("mismatched ()s while parsing '%s'" % text)
        if self.args[-1].vararg:
            self.variadic = True
            self.variadic_arg = self.args[-1]
            self.last_fixed_arg = self.args[-2].name
            self.variadic_decl = "va_list ap;\n"
            self.variadic_start = "va_start(ap, %s);\n" % self.last_fixed_arg
            if self.variadic_arg.vararg_wraps:
                self.variadic_decl += "\t%s;\n" % \
                    self.variadic_arg.vararg_wraps.decl()
                self.variadic_start += ("\t%s = va_arg(ap, %s);"
                                        "\n\tva_end(ap);\n") % \
                                       (self.variadic_arg.name,
                                        self.variadic_arg.type)
            else:
                # lie blatantly; we don't handle this case
                self.variadic = False

    # for a wrap function, the outer foo() wrapper will convert to a va_list,
    # but the inner wrap_foo() just passes the va_list through.
    def maybe_variadic_start(self):
        """Use va_arg() to grab an optional argument if needed."""
        if self.variadic and self.variadic_arg.vararg_wraps:
            return self.variadic_start
        else:
            return ""

    def maybe_variadic_decl(self):
        """Declare va_list ap and optional argument, if needed."""
        if self.variadic and self.variadic_arg.vararg_wraps:
            return self.variadic_decl
        else:
            return ""

    def decl(self, comment=False, wrap=False):
        """Produce the declaration form of this argument list."""
        if not self.args:
            return "void"

        return ', '.join(x.decl(comment=comment, wrap=wrap) for x in self.args)

    def call(self):
        """Produce the calling form of this argument list."""
        if not self.args:
            return ""
        return ', '.join([x.call() for x in self.args])

    def __repr__(self):
        if not self.args:
            return "no arguments"
        else:
            return '::'.join([x.decl() for x in self.args])

    
class Argument:
    """A function argument such as 'char *path' or 'char (*foo)(void)'"""
    def __init__(self, text):
        """get the type and name of a trivial C declaration"""
        self.vararg = False
        self.function_pointer = False
        self.spacer = ''
        
        if text == 'void':
            raise Exception("Tried to declare a nameless object of type void.")

        if text.startswith('...'):
            self.vararg = True
            if len(text) > 3:
                # we're a wrapper for something else, declared as
                # ...{real_decl}, as in the third argument to open(2)
                text = text[4:-1]
                # stash a copy of these values without the vararg flag, so
                # we can declare them prettily later
                self.vararg_wraps = Argument(text)
            else:
                # nothing to do.
                self.vararg_wraps = None
                self.type, self.name = None, None
                return
        else:
            self.vararg = False

        # try for a function pointer
        match = re.match('(.*)\(\*([a-zA-Z0-9$_]*)\)\((.*)\)', text)
        if match:
            self.function_pointer = True
            self.args = match.group(3)
            self.type = match.group(1)
            self.name = match.group(2).rstrip()
        else:
            # plain declaration
            match = re.match('(.*[ *])\(?\*?([a-zA-Z0-9$_]*)\)?', text)
            # there may not be a match, say in the special case
            # where an arg is '...'
            if match:
                self.type, self.name = match.group(1).rstrip(), match.group(2)
            else:
                self.type, self.name = None, None

        # spacing between type and name, needed if type ends with a character
        # which could be part of an identifier
        if re.match('[_a-zA-Z0-9]', self.type[-1]):
            self.spacer = ' '

    def decl(self, comment=False, wrap=False):
        """Produce the declaration form of this argument."""
        if self.function_pointer:
            decl = "%s%s(*%s)(%s)" % \
                   (self.type, self.spacer, self.name, self.args)
        else:
            decl = "%s%s%s" % (self.type, self.spacer, self.name)

        if self.vararg:
            if self.vararg_wraps:
                if comment:
                    decl = "... { %s }" % decl
                else:
                    decl = "... /* %s */" % decl
            else:
                if wrap:
                    decl = "va_list ap"
                else:
                    decl = "..."
        return decl

    def call(self):
        """Produce the call form of this argument (usually its name)."""
        if self.type == 'void':
            return ''

        if self.vararg and not self.vararg_wraps:
            return "ap"

        return self.name

    def __str__(self):
        return self.decl()

    def __repr__(self):
        return self.decl()

typedata = {
  'char *': { 'format': '%s', 'value': 'rc ? rc : "<nil>"' },
  'const char *': { 'format': '%s', 'value': 'rc ? rc : "<nil>"' },
  'DIR *': { 'format': '%p', 'value': '(void *) rc' },
  'FILE *': { 'format': '%p', 'value': '(void *) rc' },
  'FTS *': { 'format': '%p', 'value': '(void *) rc' },
  'gid_t': { 'format': '%ld', 'value': ' (long) rc' },
  'int': { 'format': '%d', 'value': 'rc' },
  'long': { 'format': '%ld', 'value': 'rc' },
  'mode_t': { 'format': '0%lo', 'value': '(long) rc' },
  'off_t': { 'format': '%lld', 'value': '(long long) rc' },
  'size_t': { 'format': '%lu', 'value': '(unsigned long) rc' },
  'ssize_t': { 'format': '%ld', 'value': '(long) rc' },
  'struct group *': { 'format': '%p', 'value': '(void *) rc' },
  'struct passwd *': { 'format': '%p', 'value': '(void *) rc' },
  'uid_t': { 'format': '%ld', 'value': ' (long) rc' },
  'void *': { 'format': '%p', 'value': 'rc' },
  'void': { 'format': 'void%s', 'value': '""' },
}

class Function:
    """A function signature and additional data about how the function works"""
    def __init__(self, port, line):
        # table of known default values:
        default_values = {
            'gid_t': '0',
            'uid_t': '0',
            'int': '-1',
            'long': '-1',
            'mode_t': '0',
            'ssize_t': '-1'
        }
    
        self.dirfd = 'AT_FDCWD'
        self.flags = '0'
        self.port = port
        self.directory = ''
        self.version = 'NULL'
        # On Darwin, some functions are SECRETLY converted to foo$INODE64
        # when called.  So we have to look those up for real_*
        self.inode64 = None
        self.real_func = None
        self.paths_to_munge = []
        self.specific_dirfds = {}
        self.hand_wrapped = None
        self.async_skip = None
        # used for the copyright date when creating stub functions
        self.date = datetime.date.today().year
    
        function, comments = line.split(';')
        comment = re.search('/\* *(.*) *\*/', comments)
        if comment:
            self.comments = comment.group(1)
        else:
            self.comments = None
    
        bits = re.match('([^(]*)\((.*)\)', function)
        type_and_name = Argument(bits.group(1))
        self.type, self.name = type_and_name.type, type_and_name.name
        # convenient to have this declared here so we can use its .decl later
        if self.type != 'void':
            self.return_code = Argument("%s rc" % self.type)
    
        # Some args get special treatment:
        # * If the arg has a name ending in 'path', we will canonicalize it.
        # * If the arg is named 'dirfd' or 'flags', it becomes the default
        #   values for the dirfd and flags arguments when canonicalizing.
        # * If the name ends in dirfd, we do the same fancy stuff.
        # * Note that the "comments" field (/* ... */ after the decl) can
        #   override the dirfd/flags values.
        self.args = ArgumentList(bits.group(2))
        for arg in self.args.args:
            # ignore varargs, they never get these special treatments
            if arg.vararg:
                pass
            elif arg.name.endswith('dirfd'):
                if len(arg.name) > 5:
                    self.specific_dirfds[arg.name[:-5]] = True
                self.dirfd = 'dirfd'
            elif arg.name == 'flags':
                self.flags = '(flags & AT_SYMLINK_NOFOLLOW)'
            elif arg.name.endswith('path'):
                self.paths_to_munge.append(arg.name)
    
        # pick default values
        if self.type == 'void':
            self.default_value = ''
        elif self.type[-1:] == '*':
            self.default_value = 'NULL'
        else:
            try:
                self.default_value = default_values[self.type]
            except KeyError:
                raise KeyError("Function %s has return type %s,"
                               "for which there is no default value." %
                               (self.name, self.type))

        # handle special comments, such as flags=AT_SYMLINK_NOFOLLOW
        if self.comments:
            modifiers = self.comments.split(', ')
            for mod in modifiers:
                key, value = mod.split('=')
                value = value.rstrip()
                setattr(self, key, value)
    
    def maybe_inode64(self):
        if self.inode64 and os.uname()[0] == 'Darwin':
            return "$INODE64"
        else:
            return ""

    def end_maybe_skip(self):
        if self.hand_wrapped:
            return """/* Hand-written wrapper for this function. */
#endif
"""
        else:
            return ""

    def maybe_skip(self):
        if self.hand_wrapped:
            return """/* Hand-written wrapper for this function. */
#if 0
"""
        else:
            return ""

    def maybe_async_skip(self):
        if self.async_skip:
            return """/* This function is not called if pseudo is configured --enable-force-async */
#ifdef PSEUDO_FORCE_ASYNC
	if (!pseudo_allow_fsync) {
		PROFILE_DONE;
		return %s;
	}
#endif
""" % self.async_skip
        else:
            return ""

    def comment(self):
        """declare self (in a comment)"""
        return self.decl(comment = True)
        
    def decl(self, comment=False, wrap=True):
        """declare self"""
        if self.type[-1:] == '*':
            spacer = ''
        else:
            spacer = ' '
        return "%s%s%s(%s)" % \
                (self.type, spacer, self.name, self.args.decl(comment, wrap))

    def decl_args(self):
        """declare argument list"""
        return self.args.decl()

    def wrap_args(self):
        """declare argument list for wrap_foo() variant"""
        return self.args.decl(wrap = True)

    def call_args(self):
        """present argument list for a function call"""
        return self.args.call()

    def fix_paths(self):
        """create/allocate canonical paths"""
        fix_paths = []
        for path in self.paths_to_munge:
            prefix = path[:-4]
            if prefix not in self.specific_dirfds:
                prefix = ''
            fix_paths.append(
                "%s = pseudo_root_path(__func__, __LINE__, %s%s, %s, %s);" %
                (path, prefix, self.dirfd, path, self.flags))
        return "\n\t\t".join(fix_paths)

    def real_predecl(self):
        if self.real_func:
            return self.decl().replace(self.name, self.real_func, 1) + ";"
        else:
            return ""

    def real_init(self):
        if self.real_func:
            return self.real_func
        else:
            return "NULL"

    def rc_return(self):
        """return rc (or just return)"""
        if self.type == 'void':
            return "return;"
        else:
            return "return rc;"

    def rc_format(self):
        """the format string to use for the return value"""
        return typedata.get(self.type, { 'format': '[%s]', 'value': '"' + self.type + '"' })['format']

    def rc_value(self):
        """the value to pass for the format string for the return value"""
        return typedata.get(self.type, { 'format': '[%s]', 'value': '"' + self.type + '"' })['value']

    def rc_decl(self):
        """declare rc (if needed)"""
        if self.type == 'void':
            return ""
        else:
            return "%s = %s;" % (self.return_code.decl(), self.default_value)

    def rc_assign(self):
        """assign something to rc (or discard it)"""
        if self.type == 'void':
            return "(void)"
        else:
            return "rc ="

    def def_return(self):
        """return default value (or just return)"""
        if self.type == 'void':
            return "return;"
        else:
            return "return %s;" % self.default_value

    def __getitem__(self, key):
        """Make this object look like a dict for Templates to use"""
        try:
            attr = getattr(self, key)
        except AttributeError:
            # There's a few attributes that are handled inside the args
            # object, so check there too...
            attr = getattr(self.args, key)

        if callable(attr):
            return attr()
        else:
            return attr

    def __repr__(self):
        pretty = "%(name)s returns %(type)s and takes " % self
        pretty += repr(self.args)
        if self.comments:
            pretty += ' (%s)' % self.comments
        return pretty

    def funcdeps(self):
        return 'pseudo_wrappers.o: ports/%s/guts/%s.c' % ( self.port, self.name )

class Port:
    """
A Port is a set of function declarations and code providing
details specific to a specific host environment, such as Linux.
Ports can override each other, and each port can indicate
additional ports to include.
"""

    def __init__(self, port, sources):
        if type(port) is not str:
            port = str(port, encoding="ascii")
        self.name = port
        self.subports = []
        self.preports = []
        print(port)

        if os.path.exists(self.portfile("pseudo_wrappers.c")):
            self.wrappers = self.portfile("pseudo_wrappers.c")
        else:
            self.wrappers = None

        if os.path.exists(self.portfile("portdefs.h")):
            self.portdef_file = self.portfile("portdefs.h")
        else:
            self.portdef_file = None

        if os.path.exists(self.portfile("wrapfuncs.in")):
            self.funcs = process_wrapfuncs(port)
        else:
            self.funcs = {}

        for source in sources:
            source.emit('port', self)

        if os.path.exists(self.portfile("preports")):
            subport_proc = subprocess.Popen([self.portfile("preports"), self.name], stdout=subprocess.PIPE)
            portlist = subport_proc.communicate()[0]
            retcode = subport_proc.poll()
            if retcode:
                raise Exception("preports script failed for port %s" % self.name)

            for preport in portlist.split():
                next = Port(preport, sources)
                self.preports.append(next)

        if os.path.exists(self.portfile("subports")):
            subport_proc = subprocess.Popen([self.portfile("subports"), self.name], stdout=subprocess.PIPE)
            portlist = subport_proc.communicate()[0]
            retcode = subport_proc.poll()
            if retcode:
                raise Exception("subports script failed for port %s" % self.name)

            for subport in portlist.split():
                next = Port(subport, sources)
                self.subports.append(next)

    def functions(self):
        mergedfuncs = {}
        for pre in self.preports:
            prefuncs = pre.functions()
            for name in prefuncs.keys():
                if name in mergedfuncs:
                    print("Warning: %s from %s overriding %s" % (name, pre.name, mergedfuncs[name].port))
                mergedfuncs[name] = prefuncs[name]
        for name in self.funcs.keys():
            if name in mergedfuncs:
                print("Warning: %s from %s overriding %s" % (name, self.name, mergedfuncs[name].port))
            mergedfuncs[name] = self.funcs[name]
        for sub in self.subports:
            subfuncs = sub.functions()
            for name in subfuncs.keys():
                if name in mergedfuncs:
                    print("Warning: %s from %s overriding %s" % (name, sub.name, mergedfuncs[name].port))
                mergedfuncs[name] = subfuncs[name]
        return mergedfuncs

    def define(self):
        return '#define PSEUDO_PORT_%s 1' % self.name.upper().replace('/', '_')

    def portdeps(self):
        deps = []
        if self.wrappers:
            deps.append(self.wrappers)
        if self.portdef_file:
            deps.append(self.portdef_file)
        if deps:
            return 'pseudo_wrappers.o: %s' % ' '.join(deps)
        else:
            return '# no extra dependencies for %s.' % self.name

    def portdefs(self):
        if self.portdef_file:
            return '#include "%s"' % self.portdef_file
        else:
            return '/* no portdefs for %s */' % self.name

    def include(self):
        if self.wrappers:
            return '#include "%s"' % self.wrappers
        else:
            return '/* no #include for %s */' % self.name

    def portfile(self, name):
        return "ports/%s/%s" % (self.name, name)

    def __getitem__(self, key):
        """Make this object look like a dict for Templates to use"""
        try:
            attr = getattr(self, key)
        except AttributeError:
            return None

        if callable(attr):
            return attr()
        else:
            return attr


def process_wrapfuncs(port):
    """Process a wrapfuncs.in file, generating a list of prototypes."""
    filename = "ports/%s/wrapfuncs.in" % port
    funcs = {}
    directory = os.path.dirname(filename)
    sys.stdout.write("%s: " % filename)
    funclist = open(filename)
    for line in funclist:
        line = line.rstrip()
        if line.startswith('#') or not line:
            continue
        try:
            func = Function(port, line)
            func.directory = directory
            funcs[func.name] = func
            sys.stdout.write(".")
        except Exception(e):
            print("Parsing failed:", e)
            exit(1)
    funclist.close()
    print("")
    return funcs

def main(argv):
    """Read in function definitions, write out files based on templates."""
    funcs = []
    sources = []

    for arg in argv:
        name, value = arg.split('=')
        os.environ["port_" + name] = value

    # error checking helpfully provided by the exception handler
    copyright_file = open('guts/COPYRIGHT')
    TemplateFile.copyright = copyright_file.read()
    copyright_file.close()

    for path in glob.glob('templates/*'):
        try:
            print("Considering template: " + path)
            source = TemplateFile(path)
            if source.name.endswith('.c') or source.name.endswith('.h'):
                source.emit('copyright')
            source.emit('header')
            sources.append(source)
        except IOError:
            print("Invalid or malformed template %s.  Aborting." % path)
            exit(1)

    try:
        port = Port('common', sources)

    except KeyError:
        print("Unknown uname -s result: '%s'." % uname_s)
        print("Known system types are:")
        print("%-20s %-10s %s" % ("uname -s", "port name", "description"))
        for key in host_ports:
            print("%-20s %-10s %s" % (key, host_ports[key],
                                      host_descrs[host_ports[key]]))

    # the per-function stuff
    print("Writing functions...")
    all_funcs = port.functions()
    for name in sorted(all_funcs.keys()):
        # populate various tables and files with each function
        for source in sources:
            source.emit('body', all_funcs[name])
    print("done.  Cleaning up.")
    
    for source in sources:
        # clean up files
        source.emit('footer')
        source.close()

if __name__ == '__main__':
    main(sys.argv[1:])
